import re, socket, sys;

DEFAULT_HTTP_PORT = 80;
DEFAULT_HTTPS_PORT = 443;
BLOCK_SIZE = 1024;
OS_VERSIONS = {
  'win7':     'Windows NT 6.1',
  'winvista': 'Windows NT 6.0',
  'winxp':    'Windows NT 5.1',
};
GET_REQUEST_TEMPLATES = {
"chrome": """%(method)s %(uri)s HTTP/%(http_version)s
Host: %(host_port)s
Connection: keep-alive
User-Agent: Mozilla/5.0 (Windows; U; %(os_version)s; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.204 Safari/534.16
Accept: application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3
""",
"firefox": """%(method)s %(uri)s HTTP/%(http_version)s
Host: %(host_port)s
User-Agent: Mozilla/5.0 (%(os_version)s; WOW64; rv:2.0) Gecko/20100101 Firefox/4.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip, deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 115
Connection: keep-alive
""",
"msie": """%(method)s %(uri)s HTTP/%(http_version)s
Accept: text/html, application/xhtml+xml, */*
Accept-Language: en-US
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; %(os_version)s; WOW64; Trident/5.0)
Accept-Encoding: gzip, deflate
Host: %(host_port)s
Connection: Keep-Alive
""",
"opera": """%(method)s %(uri)s HTTP/%(http_version)s
User-Agent: Opera/9.80 (%(os_version)s; U; en) Presto/2.7.62 Version/11.01
Host: %(host_port)s
Accept: text/html, application/xml;q=0.9, application/xhtml+xml, image/png, image/jpeg, image/gif, image/x-xbitmap, */*;q=0.1
Accept-Language: en-US,en;q=0.9
Accept-Charset: iso-8859-1, utf-8, utf-16, *;q=0.1
Accept-Encoding: deflate, gzip, x-gzip, identity, *;q=0
Connection: Keep-Alive
""",
"safari": """%(method)s %(uri)s HTTP/%(http_version)s
Host: %(host_port)s
User-Agent: Mozilla/5.0 (Windows; U; %(os_version)s; en-US) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27
Accept: application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
Accept-Language: en-US
Accept-Encoding: gzip, deflate
Connection: keep-alive
"""};

def Usage():
  print 'HTTP [GET|POST] url [options]';
  print;
  print 'Where options is one of the following:';
  print '  --save[-as="file_name"]'
  print '    Save the HTTP reply to disk. The default file name if none is provided is';
  print '    the file name at the end of the url. If there is no file name in the url,';
  print '    the host name is used instead.';
  print '  --headers, --body';
  print '    Output only the headers or the body of the HTTP reply, respectively. If';
  print '    both are provided or none is, the entire HTTP reply is output.';
  print '  --output-request';
  print '    Output the request rather than send it to the server.';
  print '  --version=[http version]';
  print '    Specify the HTTP version to use. Example values are "0.9", "1.0" and "1.1".';
  print '  --browser=[%s]' % '|'.join(GET_REQUEST_TEMPLATES.keys());
  print '    Specify which browser to emulate (the request uses similar HTTP headers';
  print '    to the specified browser). This may not be an up-to-date version of that';
  print '    browser depending on how old your copy of this script is!)';
  print '    Default: %s' % GET_REQUEST_TEMPLATES.keys()[0];
  print '  --os=[%s]' % '|'.join(OS_VERSIONS.keys());
  print '    Specify which os to emulate (the request HTTP headers contain this';
  print '    information). Not all combinations of browser/os may be valid.';
  print '    Default: %s' % OS_VERSIONS.keys()[0];
  print;
  print 'When using the POST method, an empty body (0 bytes) is send with mime type';
  print '"application/x-www-form-urlencoded".';

def Main(argv):
  if len(argv) == 0:
    Usage();
    return True
  method = argv[0].upper();
  if method not in ['GET', 'POST']:
    print >>sys.stderr, 'Only GET or POST methods are supported.';
    return False;
  if len(argv) < 2:
    url_check = False;
  else:
    url = argv[1];
    url_check = re.match(r"^(?:(https?)://)?([0-9a-z.-]+)(?:\:(\d+))?([^\?\#]*)(?:\?([^\#]*))?(?:\#(.*))?$", \
        url, re.IGNORECASE | re.DOTALL);
  if not url_check:
    print >>sys.stderr, 'Second argument must be a valid http/https url';
    return False;
  proto, host, port, path, query, anchor = url_check.groups();
  if path and path[0] == ':':
    print >>sys.stderr, 'Unknown protocol';
    return False;
  if proto is None:
    proto = 'http';
  ssl = (proto.lower() == "https");
  browser = None;
  os_version = None;
  http_version = None;
  output_request = None;
  output_headers = None;
  output_body = None;
  save = None;
  save_as = None;
  for argc in range(2, len(argv)):
    arg = argv[argc];
    if arg.startswith('--browser='):
      if browser is not None:
        print >>sys.stderr, 'You can only specify one --browser option';
        return False;
      browser = arg[len('--browser='):];
      if browser not in GET_REQUEST_TEMPLATES.keys():
        print >>sys.stderr, 'Invalid or unknown browser option: "%s"' % browser;
        print >>sys.stderr, 'Valid values: %s.' % \
            ', '.join(GET_REQUEST_TEMPLATES.keys());
    elif arg.startswith('--version='):
      if http_version is not None:
        print >>sys.stderr, 'You can only specify one --version option';
        return False;
      http_version = arg[len('--version='):];
    elif arg.startswith('--os='):
      if os_version is not None:
        print >>sys.stderr, 'You can only specify one --os option';
        return False;
      os = arg[len('--os='):].lower();
      if os not in OS_VERSIONS:
        print >>sys.stderr, 'Unknown os option';
        return False;
      os_version = OS_VERSIONS[os];
    elif arg == '--output-request':
      output_request = True;
    elif arg == '--headers':
      output_headers = True;
    elif arg == '--body':
      output_body = True;
    elif arg == '--save':
      if save_as is not None:
        print >>sys.stderr, 'You cannot specify both --save and --save-as options';
        return False;
      save = True;
    elif arg.startswith('--save-as='):
      if save_as is not None:
        print >>sys.stderr, 'You can only specify one --save-as option';
        return False;
      save = True;
      save_as = arg[len('--save-as='):];
    else:
      print >>sys.stderr, 'Invalid option "%s"' % arg;
  if not browser:
    browser = GET_REQUEST_TEMPLATES.keys()[0];
  if http_version is None:
    http_version = '1.1';
  if os_version is None:
    os_version = OS_VERSIONS[OS_VERSIONS.keys()[0]];
  if output_headers is None and output_body is None:
    output_headers = True;
    output_body = True;
  if save and save_as is None:
    start_filename = path.rfind('/') + 1;
    if start_filename != 0 and start_filename != len(path):
      save_as = path[start_filename:];
    else:
      save_as = host;
  if port is None:
    host_port = host;
    port = [DEFAULT_HTTP_PORT, DEFAULT_HTTPS_PORT][ssl];
  else:
    host_port = "%s:%d" % (host, port);
    port = int(port);
  if path == "":
    uri = "/";
  else:
    uri = path;
  if query:
    uri += '?' + query;
  request = GET_REQUEST_TEMPLATES[browser] % {
      "method": method,
      "host_port": host_port,
      "uri": uri,
      'http_version': http_version,
      'os_version': os_version,
  };
  if method == 'POST':
    request += 'Content-Length: 0\r\n' \
              'Content-Type: application/x-www-form-urlencoded\r\n';
  request += '\r\n';
  if output_request:
    print request;
    return True;
  host_ip = socket.gethostbyname(host);
  connection = socket.socket();
  try:
    connection.connect((host_ip, port));
  except socket.error, (code, message):
    print >>sys.stderr, "Socket error %d while connecting to %s:%d - %s" % (code, host_ip, port, message);
    return False;
  connection.send(request);
  buffer = "";
  content_length = None;
  headers = "";
  in_header = True;
  while in_header:
    block = connection.recv(BLOCK_SIZE);
    buffer += block;
    while 1:
      line_break_index = buffer.find("\n");
      if line_break_index > -1:
        line = buffer[:line_break_index + 1];
        buffer = buffer[line_break_index + 1:];
        headers += line;
        if line == "\r\n":
          in_header = False;
          if not save and output_headers and output_body: print repr(line)[1:-1];
          break;
        if not save and output_headers: print repr(line)[1:-1];
        content_length_match = re.match(r"^\s*content-length:\s*(\d+)\s*\r\n$", line, re.IGNORECASE);
        if content_length_match is not None:
          content_length = int(content_length_match.group(1));
  body = buffer;
  if content_length is not None:
    while len(body) != content_length:
      read_bytes = min(BLOCK_SIZE, content_length - len(body));
      block = connection.recv(read_bytes);
      body += block;

  if save:
    output = "";
    if output_headers:  output += headers;
    if output_body:     output += body;
    try:
      file = open(save_as, 'wb');
    except Exception, e:
      print >>sys.stderr, 'Cannot open file "%s"' % save_as;
      print >>sys.stderr, 'Error:' % e.message;
      return False;
    try:
      file.write(output);
    finally:
      file.close();
    print 'Saved %d bytes to "%s".' % (len(output), save_as);
  else:
    while body: 
      line_break_index = body.find("\n");
      if line_break_index == -1:
        line = body;
        body = None;
      else:
        line = body[:line_break_index + 1];
        body = body[line_break_index + 1:];
      print repr(line)[1:-1];
  return True;

if __name__ == "__main__":
  sys.exit({True:0, False:1}[Main(sys.argv[1:])]);